package org.unit.testmgmt.generator.wizard;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Vector;

import org.eclipse.jdt.core.ILocalVariable;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.ui.JavaElementLabelProvider;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.widgets.Control;
import org.unit.testmgmt.messages.Messages;
import org.unit.testmgmt.tml.Assertion;
import org.unit.testmgmt.tml.AssertionType;
import org.unit.testmgmt.tml.Method;
import org.unit.testmgmt.tml.TestCase;
import org.unit.testmgmt.tml.UTMModel;
import org.unit.testmgmt.ui.generator.wizard.GeneratorWizardBasePage;
import org.unit.testmgmt.ui.generator.wizard.GeneratorWizardTestCasesPage;
import org.unit.testmgmt.ui.table.ComboboxEntry;
import org.unit.testmgmt.ui.table.ITableViewerColumnListener;
import org.unit.testmgmt.ui.table.TableEntryGeneric;
import org.unit.testmgmt.ui.table.TableEntryList;
import org.unit.testmgmt.ui.table.TableViewerBase;
import org.unit.testmgmt.ui.table.TableViewerColumn;
import org.unit.testmgmt.ui.table.TableViewerColumn.Columntype;
import org.unit.testmgmt.ui.table.TableViewerColumns;

/**
 * The controller for the test cases page.
 * 
 * @author Robert Streng
 * 
 */
public class GeneratorWizardTestCases extends GeneratorWizardBase {

	private Vector<IMethod> methods;

	private HashMap<IMethod, TableEntryList<TableEntryGeneric>> testcasesMap = new HashMap<IMethod, TableEntryList<TableEntryGeneric>>();

	private TableViewerColumn colTestcaseDescription = new TableViewerColumn("description",
			SWT.LEFT, Columntype.STRING);

	private TableViewerColumn colAssertionBase = null;
	private TableViewerColumn colAssertionType = new TableViewerColumn("assertion-type",
			SWT.LEFT, Columntype.STRING, createAssertionTypeCboboxEntries());
	private TableViewerColumn colAssertionValue = new TableViewerColumn("value", SWT.LEFT,
			Columntype.STRING);
	private TableViewerColumn colAssertionMsg = new TableViewerColumn("message", SWT.LEFT,
			Columntype.STRING);

	private Vector<IMethod> baseclassMethods;

	private HashMap<TableEntryGeneric, TableEntryList<TableEntryGeneric>> assertionsMap = new HashMap<TableEntryGeneric, TableEntryList<TableEntryGeneric>>();

	private TableViewerBase tblAssertions;

	private TableViewerColumn colTestbaseId = null;

	private TableViewerBase tblTestcases = null;

	private boolean lastTestBaseIdUnique = true;

	private boolean listenerAdded = false;

	private boolean initialized = false;

	/**
	 * Constructor
	 * 
	 * @param model
	 * @param generatorWizardPageBase
	 */
	public GeneratorWizardTestCases(UTMModel model, GeneratorWizardBasePage generatorWizardPageBase) {
		super(model, generatorWizardPageBase);
		baseclassMethods = model.getUtmElements().getConstructorsAndMethods().getBaseClassMethods();
	}

	@Override
	protected GeneratorWizardTestCasesPage getPage() {
		return (GeneratorWizardTestCasesPage) super.getPage();
	}

	@Override
	public void initPage() {
		GeneratorWizardTestCasesPage page = getPage();
		initCboMethod(page, true, null);
		addListener(page);
		initialized = true;
	}

	/**
	 * This method is called whenn the test base id changed.
	 * 
	 * @param column
	 * @param value
	 * @param oldValue
	 */
	protected void valueChangedTestbaseIds(TableViewerColumn column, Object value,
			Object oldValue) {
		refreshTestcases(column, value, oldValue);
	}

	/**
	 * Refreshes the test cases.
	 * 
	 * @param column
	 * @param value
	 * @param oldValue
	 */
	private void refreshTestcases(TableViewerColumn column, Object value, Object oldValue) {
		boolean testBaseIdUnique = getPage().getTestbasesPage().getController()
				.isTestbaseIdUnique();

		if (!testBaseIdUnique || !this.lastTestBaseIdUnique) {
			handleMethodSelection(getPage().getCboMethod().getSelection());
			lastTestBaseIdUnique = testBaseIdUnique;
			return;
		}
		lastTestBaseIdUnique = testBaseIdUnique;

		Object valueTestBaseId;

		if (colTestbaseId != null) {
			colTestbaseId = createColTestbaseId();
		}
		Iterator<ComboboxEntry> iterator = colTestbaseId.getComboboxEntries().iterator();
		ComboboxEntry firstCboEntry = null;
		if (iterator.hasNext()) {
			firstCboEntry = iterator.next();
		}

		for (TableEntryList<TableEntryGeneric> tblEntriesList : testcasesMap.values()) {
			for (TableEntryGeneric tblEntry : tblEntriesList.getTableEntries()) {
				valueTestBaseId = tblEntry.getValue(colTestbaseId);
				if (valueTestBaseId instanceof ComboboxEntry) {
					ComboboxEntry cboEntryTestBase = (ComboboxEntry) tblEntry
							.getValue(colTestbaseId);

					if (cboEntryTestBase.getLabel().equals(oldValue)) {
						if (value != null) {
							cboEntryTestBase.setLabel(value.toString());
							tblEntry.setValue(colTestbaseId, cboEntryTestBase);
						}
						else
							tblEntry.setValue(colTestbaseId, firstCboEntry);
					}
				}
			}
		}

		handleMethodSelection(getPage().getCboMethod().getSelection());
	}

	/**
	 * Handle the method selection.
	 * 
	 * @param selection
	 */
	protected void handleMethodSelection(ISelection selection) {
		if (selection instanceof IStructuredSelection) {
			IStructuredSelection structSelectionObj = (IStructuredSelection) selection;
			Object selectionObj = structSelectionObj.getFirstElement();

			if (selectionObj instanceof IMethod) {
				createTblTestcases((IMethod) selectionObj);
			}
		}
	}

	/**
	 * Handle the test case selection.
	 * 
	 * @param tblTestCaseEntry
	 */
	protected void handleTestCaseSelection(TableEntryGeneric tblTestCaseEntry) {
		createTblAssertions(tblTestCaseEntry);
	}

	/**
	 * Add the listener to the page.
	 * 
	 * @param page
	 */
	private void addListener(GeneratorWizardTestCasesPage page) {
		if (listenerAdded)
			return;

		listenerAdded = true;

		page.getCboMethod().addSelectionChangedListener(new ISelectionChangedListener() {

			@Override
			public void selectionChanged(SelectionChangedEvent event) {
				ISelection selection = event.getSelection();
				handleMethodSelection(selection);
			}
		});

		getColTblTestBasesTbId().addChangeListener(new ITableViewerColumnListener() {

			@Override
			public void tblEntryAdded(TableViewerColumn column, Object newValue) {
				valueChangedTestbaseIds(column, newValue, null);
			}

			@Override
			public void tblEntryRemoved(TableViewerColumn column, Object oldValue) {
				valueChangedTestbaseIds(column, null, oldValue);
			}

			@Override
			public void tblEntryChanged(TableViewerColumn column, Object value, Object oldValue) {
				valueChangedTestbaseIds(column, value, oldValue);
			}

		});

		page.getMainPage().getCheckboxTreeViewer().addCheckStateListener(new ICheckStateListener() {

			@Override
			public void checkStateChanged(CheckStateChangedEvent arg0) {
				Object checkedElement = arg0.getElement();
				if (arg0.getChecked()) {

					if (checkedElement instanceof IMethod) {
						initCboMethod(getPage(), false, (IMethod) checkedElement);
					}
					else if (checkedElement instanceof IType) {
						initCboMethod(getPage(), true, null);
					}
				}
				else {
					initCboMethod(getPage(), false, null);
				}
			}
		});
	}

	/**
	 * Creates the table for the test cases.
	 * 
	 * @param selectedMethod
	 */
	@SuppressWarnings("unchecked")
	private void createTblTestcases(IMethod selectedMethod) {
		disposeTables();

		TableViewerColumns tblColumns = createTblTestCasesColumns(selectedMethod);

		TableEntryList<TableEntryGeneric> tblEntryList;

		if ((tblEntryList = testcasesMap.get(selectedMethod)) == null) {
			tblEntryList = new TableEntryList<TableEntryGeneric>(TableEntryGeneric.class,
					tblColumns);
			testcasesMap.put(selectedMethod,
					(TableEntryList<TableEntryGeneric>) tblTestcases.getTableEntryList());

			throw new RuntimeException(Messages.General_error_processing);
		}

		tblTestcases = new TableViewerBase(getPage().getGrpTblTestcases(), tblColumns, tblEntryList);

		// layout
		getPage().layoutTblTestcases(tblTestcases);

		// add listener
		tblTestcases.getTable().addSelectionListener(new SelectionListener() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				IStructuredSelection selection = (IStructuredSelection) tblTestcases.getSelection();
				handleTestCaseSelection((TableEntryGeneric) selection.getFirstElement());
			}

			@Override
			public void widgetDefaultSelected(SelectionEvent e) {
				// nothing
			}
		});

		if (initialized) {
			Object tblEntry = tblTestcases.getTableViewer().getElementAt(0);
			if (tblEntry instanceof TableEntryGeneric) {
				handleTestCaseSelection((TableEntryGeneric) tblEntry);
			}
		}
	}

	/**
	 * Dispose the tables.
	 */
	private void disposeTables() {
		// delete old elements
		for (Control control : getPage().getGrpTblTestcases().getChildren()) {
			control.dispose();
		}
		for (Control control : getPage().getGrpTblAssertions().getChildren()) {
			control.dispose();
		}
	}

	/**
	 * Create the columns of the table test cases.
	 * 
	 * @param selectedMethod
	 * @return columns of the table test cases
	 */
	private TableViewerColumns createTblTestCasesColumns(IMethod selectedMethod) {
		TableViewerColumns tblColumns = new TableViewerColumns();

		// standard columns
		tblColumns.addColumn(colTestcaseDescription);
		tblColumns.addColumn(createColTestbaseId());

		// parameter columns
		String typeSignature;
		try {
			for (ILocalVariable param : selectedMethod.getParameters()) {
				typeSignature = param.getTypeSignature();
				tblColumns.addColumn(TableViewerColumns.createColumn(param.getElementName(),
						Signature.getSignatureSimpleName(typeSignature)));
			}
		}
		catch (JavaModelException e) {
			throw new RuntimeException(
					Messages.GeneratorWizardTestBases_ErrorWhileGetParamsFromSelectedMethod);
		}

		return tblColumns;
	}

	/**
	 * Creates the table for the assertions.
	 * 
	 * @param tblEntry
	 */
	@SuppressWarnings("unchecked")
	private void createTblAssertions(TableEntryGeneric tblEntry) {
		// delete old elements
		for (Control control : getPage().getGrpTblAssertions().getChildren())
			control.dispose();

		TableViewerColumns tblColumns = createTblAssertionsColumns();

		TableEntryList<TableEntryGeneric> tblEntryList;

		if ((tblEntryList = assertionsMap.get(tblEntry)) == null) {
			tblEntryList = new TableEntryList<TableEntryGeneric>(TableEntryGeneric.class,
					tblColumns);
		}

		tblAssertions = new TableViewerBase(getPage().getGrpTblAssertions(), tblColumns,
				tblEntryList);

		// layout
		getPage().layoutTblAssertions(tblAssertions);

		// save test-cases
		if (!assertionsMap.containsKey(tblEntry)) {
			assertionsMap.put(tblEntry,
					(TableEntryList<TableEntryGeneric>) tblAssertions.getTableEntryList());
		}
	}

	/**
	 * Creates the columns of the table assertions.
	 * 
	 * @return columns of the table assertions
	 */
	private TableViewerColumns createTblAssertionsColumns() {
		TableViewerColumns tblColumns = new TableViewerColumns();

		// standard columns
		tblColumns.addColumn(getColAssertionBase());
		tblColumns.addColumn(colAssertionType);
		tblColumns.addColumn(colAssertionValue);
		tblColumns.addColumn(colAssertionMsg);

		return tblColumns;
	}

	/**
	 * @return column assertion base
	 */
	private TableViewerColumn getColAssertionBase() {
		if (colAssertionBase == null) {
			colAssertionBase = new TableViewerColumn("base", SWT.LEFT, Columntype.STRING,
					createAssertionBaseEntries());
		}

		return colAssertionBase;
	}

	/**
	 * Creats the assertion base entries.
	 * 
	 * @return assertions base entries
	 */
	private Set<ComboboxEntry> createAssertionBaseEntries() {
		Set<ComboboxEntry> assertionBaseEntries = new HashSet<ComboboxEntry>();

		ComboboxEntry entry;
		int i = 0;

		entry = new ComboboxEntry();
		entry.setKey(i++);
		entry.setLabel("{result}");
		entry.setValue("{result}");

		assertionBaseEntries.add(entry);

		String methodLabel;
		JavaElementLabelProvider labelProvider = new JavaElementLabelProvider(
				JavaElementLabelProvider.SHOW_DEFAULT | JavaElementLabelProvider.SHOW_RETURN_TYPE
						| JavaElementLabelProvider.SHOW_TYPE);

		for (IMethod baseClassMethod : baseclassMethods) {
			try {
				if (baseClassMethod.getParameters().length != 0
						|| baseClassMethod.getReturnType() == null) {
					continue;
				}
			}
			catch (JavaModelException e) {
				continue;
			}

			methodLabel = labelProvider.getText(baseClassMethod);

			entry = new ComboboxEntry();
			entry.setKey(i++);
			entry.setLabel(methodLabel);
			entry.setValue(baseClassMethod);

			assertionBaseEntries.add(entry);
		}

		return assertionBaseEntries;
	}

	/**
	 * Creates the column for the test base ids.
	 * 
	 * @return column test base id
	 */
	private TableViewerColumn createColTestbaseId() {
		colTestbaseId = new TableViewerColumn("testbase-id", SWT.LEFT, Columntype.STRING,
				createTestbaseIdCboboxEntries());

		return colTestbaseId;
	}

	/**
	 * Creates the test base id entries .
	 * 
	 * @return test base id entries
	 */
	private Set<ComboboxEntry> createTestbaseIdCboboxEntries() {
		Set<ComboboxEntry> testbaseIds = new HashSet<ComboboxEntry>();
		ComboboxEntry entry;
		int i = 0;
		for (TableEntryList<TableEntryGeneric> testbasesList : getPage().getTestbasesPage()
				.getController().getTestbases().values()) {
			for (TableEntryGeneric tblEntry : testbasesList.getTableEntries()) {
				entry = new ComboboxEntry();
				entry.setKey(i++);
				entry.setLabel((String) tblEntry.getValue(getColTblTestBasesTbId()));
				entry.setValue(tblEntry);
				testbaseIds.add(entry);
			}
		}

		return testbaseIds;
	}

	/**
	 * Creates the assertion type entries.
	 * 
	 * @return assertion type entries
	 */
	private Set<ComboboxEntry> createAssertionTypeCboboxEntries() {
		Set<ComboboxEntry> cboAsserstionTypeEntries = new HashSet<ComboboxEntry>();
		ComboboxEntry comboboxEntry;
		int i = 0;

		for (AssertionType assertionType : AssertionType.values()) {
			comboboxEntry = new ComboboxEntry(i++, assertionType.value(), assertionType);
			cboAsserstionTypeEntries.add(comboboxEntry);
		}

		return cboAsserstionTypeEntries;
	}

	/**
	 * Initializes the cbobox method.
	 * 
	 * @param page
	 * @param refreshTables
	 * @param checkedMethod
	 */
	private void initCboMethod(GeneratorWizardTestCasesPage page, boolean refreshTables,
			IMethod checkedMethod) {

		IMethod method;
		page.getCboMethod().getCombo().removeAll();
		methods = page.getMainPage().getController().getCheckedMethods();

		Iterator<IMethod> iterator = methods.iterator();
		while (iterator.hasNext()) {
			method = iterator.next();
			page.getCboMethod().add(method);

			if (refreshTables) {
				initTblTestcases(method);
			}
		}

		if (!refreshTables) {
			disposeTables();
		}

		if (checkedMethod != null) {
			initTblTestcases(checkedMethod);
		}

	}

	/**
	 * Initializes the table test cases.
	 * 
	 * @param method
	 */
	private void initTblTestcases(IMethod method) {
		Method tmlMethod = getModel().getMethodMap().get(method);

		// create table-columns and model
		TableViewerColumns tblTestCaseColumns = createTblTestCasesColumns(method);
		TableEntryList<TableEntryGeneric> tblTestCasesEntryList = new TableEntryList<TableEntryGeneric>(
				TableEntryGeneric.class, tblTestCaseColumns);
		TableEntryGeneric tblTestCasesEntry;

		if (tmlMethod != null) {
			for (TestCase tmlTestCase : tmlMethod.getTestCase()) {
				tblTestCasesEntry = tblTestCasesEntryList.createTableEntry();

				// test-description
				tblTestCasesEntry.setValue(tblTestCaseColumns.getColumn(0), tmlTestCase.getName());

				// test-base-reference
				for (ComboboxEntry cboEntry : colTestbaseId.getComboboxEntries()) {

					TableEntryGeneric tblEntryTblTestbase = (TableEntryGeneric) cboEntry.getValue();
					String testBaseId = tblEntryTblTestbase.getValue(getColTblTestBasesTbId())
							.toString();

					if (tmlTestCase.getTestBase().equals(testBaseId)) {
						tblTestCasesEntry.setValue(tblTestCaseColumns.getColumn(1), cboEntry);
					}
				}

				// parameters
				int i = 2;
				for (String paramValue : tmlTestCase.getParamValue()) {
					tblTestCasesEntry.setValue(tblTestCaseColumns.getColumn(i++), paramValue);
				}

				// assertions
				initTblAssertions(tmlTestCase.getAssertion(), tblTestCasesEntry);
			}
		}

		if (tblTestCasesEntryList.getTableEntries().size() == 0) {
			TableEntryGeneric tblEntryTestCase = tblTestCasesEntryList.createTableEntry(true);
			tblEntryTestCase.setValue(colTestcaseDescription, "test1");
		}

		testcasesMap.put(method, tblTestCasesEntryList);
	}

	/**
	 * Initializes the table assertions.
	 * 
	 * @param tmlAssertions
	 * @param tblTestCasesEntry
	 */
	private void initTblAssertions(List<Assertion> tmlAssertions,
			TableEntryGeneric tblTestCasesEntry) {

		// create table-columns and model
		TableViewerColumns tblAssertionsColumns = createTblAssertionsColumns();
		TableEntryList<TableEntryGeneric> tblAssertionsEntryList = new TableEntryList<TableEntryGeneric>(
				TableEntryGeneric.class, tblAssertionsColumns);
		TableEntryGeneric tblAssertionsEntry;

		for (Assertion tmlAssertion : tmlAssertions) {
			tblAssertionsEntry = tblAssertionsEntryList.createTableEntry();

			for (ComboboxEntry cboAssertionBaseEntry : colAssertionBase.getComboboxEntries()) {
				Object value = cboAssertionBaseEntry.getValue();
				if (value instanceof IMethod) {
					if (((IMethod) value).getElementName().equals(tmlAssertion.getBase())) {
						tblAssertionsEntry.setValue(colAssertionBase, cboAssertionBaseEntry);
						break;
					}
				}
				if (value.equals(tmlAssertion.getBase())) {
					tblAssertionsEntry.setValue(colAssertionBase, cboAssertionBaseEntry);
				}
			}

			for (ComboboxEntry cboAssertionTypeEntry : colAssertionType.getComboboxEntries()) {
				if (cboAssertionTypeEntry.getValue().equals(tmlAssertion.getType())) {
					tblAssertionsEntry.setValue(colAssertionType, cboAssertionTypeEntry);
					break;
				}
			}
			tblAssertionsEntry.setValue(colAssertionValue, tmlAssertion.getValue());
			tblAssertionsEntry.setValue(colAssertionMsg, tmlAssertion.getMessage());
		}

		assertionsMap.put(tblTestCasesEntry, tblAssertionsEntryList);
	}

	@Override
	public void updateModel() {
		HashMap<IMethod, Method> methodMap = getModel().getMethodMap();

		int colIndex;
		List<TestCase> tmlTestcaseList;
		TestCase tmlTestCase;
		Method tmlMethod;

		for (Entry<IMethod, TableEntryList<TableEntryGeneric>> testcasesEntry : testcasesMap
				.entrySet()) {
			tmlMethod = methodMap.get(testcasesEntry.getKey());

			if (tmlMethod == null)
				continue;

			tmlTestcaseList = tmlMethod.getTestCase();
			tmlTestcaseList.clear(); // delete old test-case entries

			for (TableEntryGeneric tblTestCasesEntry : testcasesEntry.getValue().getTableEntries()) {
				tmlTestCase = getObjectFactory().createTestCase();
				tmlTestcaseList.add(tmlTestCase);

				colIndex = 0;

				for (Object value : tblTestCasesEntry.getValues()) {

					if (colIndex == 0) {
						// test-description
						tmlTestCase.setName(tblTestCasesEntry.getValue(colTestcaseDescription)
								.toString());
					}
					else if (colIndex == 1) {
						// test-base-id
						tmlTestCase.setTestBase(tblTestCasesEntry.getLabel(colTestbaseId
								.getColumnIndex()));
					}
					else {
						// test-parameter
						tmlTestCase.getParamValue().add(value.toString());
					}
					colIndex++;

				}

				updateAssertions(tmlTestCase, tblTestCasesEntry);
			}
		}
	}

	/**
	 * Updates the assertions from the page.
	 * 
	 * @param tmlTestCase
	 * @param tblTestCasesEntry
	 */
	private void updateAssertions(TestCase tmlTestCase, TableEntryGeneric tblTestCasesEntry) {
		Assertion tmlAssertion;
		Object colValue, assertionBase;
		ComboboxEntry cboAssertionBase;

		TableEntryList<TableEntryGeneric> tblAssertionsEntryList = assertionsMap
				.get(tblTestCasesEntry);

		if (tblAssertionsEntryList != null) {
			for (TableEntryGeneric tblAssertionsEntry : tblAssertionsEntryList.getTableEntries()) {
				tmlAssertion = getObjectFactory().createAssertion();
				tmlTestCase.getAssertion().add(tmlAssertion);

				colValue = tblAssertionsEntry.getValue(colAssertionBase);

				if (colValue instanceof ComboboxEntry) {
					cboAssertionBase = (ComboboxEntry) colValue;
					assertionBase = cboAssertionBase.getValue();

					if (assertionBase instanceof IMethod) {
						tmlAssertion.setBase(((IMethod) assertionBase).getElementName());
						try {
							String returnType = ((IMethod) assertionBase).getReturnType();
							returnType = Signature.getSignatureSimpleName(returnType);
							tmlAssertion.setBaseType(returnType);
						}
						catch (JavaModelException e) {
							// nothing
						}
					}
					else {
						tmlAssertion.setBase("" + colValue);
					}
				}
				else {
					tmlAssertion.setBase("" + colValue);
				}

				Object assertionTypeValue;
				assertionTypeValue = tblAssertionsEntry.getValue(colAssertionType);
				if (assertionTypeValue instanceof ComboboxEntry) {
					ComboboxEntry cboAssertionTypeEntry = (ComboboxEntry) assertionTypeValue;
					if (cboAssertionTypeEntry.getValue() instanceof AssertionType) {
						tmlAssertion.setType((AssertionType) cboAssertionTypeEntry.getValue());
					}
				}

				tmlAssertion.setValue("" + tblAssertionsEntry.getValue(colAssertionValue));
				tmlAssertion.setMessage("" + tblAssertionsEntry.getValue(colAssertionMsg));
			}
		}
	}

	/**
	 * @return column test bases id
	 */
	private TableViewerColumn getColTblTestBasesTbId() {
		return getPage().getTestbasesPage().getController().getColTestbaseId();
	}

}
